import * as THREE from 'three';
import * as utils from '../../core/utils.js';
import Application from './Application.js';
import Contours from './Contours.js';
import Vehicles from './Vehicles.js';
import Paths from './Paths.js';
import Labels from './Labels.js';
import _ from 'lodash';

/**
 * Applications, based on threejs.
 */
// let updateContoursLastTime = null,
//     updateTag = false;
let APP_3D_INSTANCE = null;
class App extends Application {
    constructor() {
        //eslint-disable-line
        super();

        this._name = 'App';

        this.resources = {};
    
        this.contours = new Contours(this);
        this.vehicles = new Vehicles(this);
        this.paths = new Paths(this);
        this.labels = new Labels();
        this.sprites = [];
        this.selectedVehicles = [];
        this.selectedRoadContours = []
        // console.log('App.constructor: ', this);

        this.renderLimit = 1000; // rerender limit when update
        this.updateContoursDebounce = _.debounce(this.createContours, this.renderLimit);
        this.updatePathsDebounce = _.debounce(this.createPaths, this.renderLimit);
    }
    static getInstance() {
        if (APP_3D_INSTANCE) {
            // console.log('App.getInstance: ', APP_3D_INSTANCE);
            if (!APP_3D_INSTANCE.contours) APP_3D_INSTANCE.contours = new Contours(this);
            if (!APP_3D_INSTANCE.vehicles) APP_3D_INSTANCE.vehicles = new Vehicles(this);
            if (!APP_3D_INSTANCE.paths) APP_3D_INSTANCE.paths = new Paths(this);
            if (!APP_3D_INSTANCE.labels) APP_3D_INSTANCE.labels = new Labels();
        } else APP_3D_INSTANCE = new App();
        return APP_3D_INSTANCE;
    }

    setStaticPath(path) {
        super.setStaticPath(path);
        this.contours.setStaticPath(path);
        this.vehicles.setStaticPath(path);
        this.labels.setStaticPath(path);
    }

    setDefaultOffset({ x = 0, y = 0, z = 0 } = {}) {
        super.setDefaultOffset({ x, y, z });
        this.contours.setOffset({ x, y, z });
        this.vehicles.setOffset({ x, y, z });
        this.paths.setOffset({ x, y, z });
        this.labels.setOffset({ x, y, z });
    }

    init(param) {
        super.init(param);
        console.log('App.init: ', param);
        // this.contours = new Contours(this);
        // this.vehicles = new Vehicles(this);
        // this.paths = new Paths(this);
        // this.labels = new Labels();

        this.initResourcesGroup();
        return this;
    }
    initResourcesGroup() {

        if (this.modelsGroup) this.scene.remove(this.modelsGroup);
        this.modelsGroup = new THREE.Group();
        this.modelsGroup.name = 'modelsGroup';
        this.modelsGroup.rotation.x = Math.PI / -2;
        this.scene.add(this.modelsGroup);

        if (this.labelsGroup) this.scene.remove(this.labelsGroup);
        this.labelsGroup = new THREE.Group();
        this.labelsGroup.name = 'labelsGroup';
        this.labelsGroup.rotation.x = Math.PI / -2;
        this.scene.add(this.labelsGroup);
    }
    initModelsGroup() {
        if (this.modelsGroup) this.scene.remove(this.modelsGroup);
        this.modelsGroup = new THREE.Group();
        this.modelsGroup.name = 'modelsGroup';
        this.modelsGroup.rotation.x = Math.PI / -2;
        this.scene.add(this.modelsGroup);
    }

    destory() {
        // console.warn('app.destory: ');
        super.destory();
        // if (this.viewport && this.viewport.el) this.viewport.el.parentElement.removeChild(this.viewport.el);
        //     this.resetCaches();
        //     // this.contours.destory();
        //     // this.vehicles.destory();
        //     this.resources = {};
        //     // this.vehiclesGroup = null;
        //     this.pathsGroup = null;
        //     this.modelsGroup = null;
        //     if (this.contours && typeof this.contours.destory === 'function') this.contours.destory();
        APP_3D_INSTANCE = null;
    }

    pointerMoveHandler(obj) {
        let res = [];
        if (obj) {
            if (this._cusorUuid === obj.uuid) return;
            this._cusorUuid = obj.uuid;
            // console.log('---> ', obj, Object.keys(this._meshCache).length, Object.keys(this._groupCache).length);
            res = this.getCacheMeshs(obj);
        }
        if (typeof this.settings.pointerMoveCallback === 'function') this.settings.pointerMoveCallback(res);
    }
    clickHandler(obj, instanceId) {
        // console.error('TEST-App.clickHandler: ', obj, !obj, instanceId);
        if (!obj) return;
        if (obj === 'empty') {
            if (typeof this.settings.clickCallback === 'function')
                this.settings.clickCallback('obj', 'instanceId', 'res'); //eslint-disable-line
        } else if (typeof this.settings.clickCallback === 'function') {
            let res = this.getCacheGroup(obj);
            this.settings.clickCallback(obj, instanceId, res);
        }
    }
    isVehicle(mesh) {
        if (mesh && mesh.userData && mesh.userData.isVehicle) return true;
        return false;
    }
    setSelectedVehicles(val) {
        if (!this.settings.selectable) val = null;
        if (!val) this.selectedVehicles = [];
        else this.selectedVehicles = val;
    }
    setSelectedContours(mesh) {
        let isSelected = false;
        if (this.selectedRoadContours && this.selectedRoadContours.includes(mesh)) {
            this.clearOutline();
            this.selectedRoadContours = [];
            isSelected = false;
        } else {
            this.setOutline(mesh);
            this.selectedRoadContours.push(mesh);
            // this.handleShowElNotification(mesh.userData.properties.roadId);
            isSelected = true;
        }
        this.artist.composer.render();
        return isSelected;
    }
    setSelectedRoadContours(roadId){
        if (this.selectedRoadContours.length > 0 && this.selectedRoadContours[0].properties.roadId == roadId) {
            this.clearOutline();
            this.artist.composer.render();
            return;
        }
        this.selectedRoadContours = [];
        for (var contourObj of this.resources.contours){
            var currRoadId = contourObj.properties.roadId;
            if (currRoadId && currRoadId == roadId) {
                this.selectedRoadContours.push(_.cloneDeep(contourObj));
            }
        }
        let meshes = [];
        //描绘选中的2D图形
        //遍历全部的图形查找选中的那些
        this.director.modelsMesh.forEach(mesh => {
            //循环遍历每一个选中的图形
            this.selectedRoadContours.forEach(contour => {
                if (mesh.userData.id && mesh.userData.id == contour.id) {
                    meshes.push(mesh);
                    return;
                }
            });
        });
        this.setOutlines(meshes);
        this.artist.composer.render();
    }
    getIntersectMesh() {
        let mesh = [];
        // let modelsGroup = this.scene.getObjectByName('modelsGroup');
        if (this.modelsGroup) this.modelsGroup.traverse(o => (o.isMesh ? mesh.push(o) : ''));
        if (this.contours.group) this.contours.group.traverse(o => (o.isMesh ? mesh.push(o) : ''));
        // if (this.vehiclesGroup) this.vehiclesGroup.traverse(o => (o.isMesh ? mesh.push(o) : ''));
        if (this.vehicles && this.vehicles.group) this.vehicles.group.traverse(o => (o.isMesh ? mesh.push(o) : ''));
        return mesh;
    }
    getCacheGroup(obj) {
        if (!this._groupCache) this._groupCache = {};
        if (this._groupCache[obj.uuid]) return this._groupCache[obj.uuid];
        let curGroup = null;
        this.modelsGroup.traverse(o => (o.children.includes(obj) === true ? (curGroup = o) : undefined));
        if (!curGroup)
            this.contours.group.traverse(o => (o.children.includes(obj) === true ? (curGroup = o) : undefined));
        if (!curGroup && this.vehicles && this.vehicles.group)
            this.vehicles.group.traverse(o => (o.children.includes(obj) === true ? (curGroup = o) : undefined));
        if (!curGroup) return null;
        this._groupCache[obj.uuid] = curGroup;
        return curGroup;
    }
    getCacheMeshs(obj) {
        if (!this._meshCache) this._meshCache = {};
        if (this._meshCache[obj.uuid]) return this._meshCache[obj.uuid];
        let curGroup = this.getCacheGroup(obj);
        if (!curGroup) return null;
        let curMeshs = [];
        curGroup.traverse(o => (o.isMesh ? curMeshs.push(o) : undefined));
        this._meshCache[obj.uuid] = curMeshs;
        return curMeshs;
    }
    resetCaches() {
        // console.log('---> resetCaches');
        this._groupCache = {};
        this._meshCache = {};
        // this.modelsGroup = null;
        this.modelsMesh = null;
        if (this.director) this.director.modelsMesh = null;
    }

    getTrayByMesh(mesh, root) {
        // let rtn = {
        //     id: '',
        //     pid: mesh.userData.contourId
        // };
        let rtn = {
            id: '',
            pid: ''
        };
        if (mesh.userData && mesh.userData.contourId) rtn.pid = mesh.userData.contourId;
        // if (root && root.children && root.children.length) rtn.pid = root.children[0].name;
        let p = root && root.parent && root.parent.position;
        if (mesh && mesh.userData && mesh.userData.name && mesh.userData.name.indexOf('-') >= 0)
            rtn.id = mesh.userData.name;
        if (!rtn.id && root && root.userData && root.userData.name && root.userData.name.indexOf('-') >= 0) {
            rtn.id = root.userData.name;
            p = root.parent.parent.position;
        }
        if (p) {
            rtn.lx = p.x;
            rtn.ly = p.z;
            rtn.lz = -p.y;
        }
        return rtn;
    }
    setShelfStatus(id, status) {
        if (!id || !status || !status.length) return;
        // if (id === '373') console.log('---> ', id, status);
        // if (id === '373') console.log('---> ', id, this.modelsGroup);
        // if (id === '373') console.log('---> ', id, this._shelvesCache);
        if (!this._shelvesCache || !Object.keys(this._shelvesCache).length) this.initShelvesCache();
        for (let i = 0, len = status.length; i < len; i++) {
            for (let j = 0, jlen = status[i].length; j < jlen; j++) {
                this.setShelfTrayStatus(id, i, j, status[i][j]);
            }
        }
    }
    initShelvesCache() {
        if (!this.modelsGroup) return;
        this._shelvesCache = {};
        // this.modelsGroup.traverse(o => (o.userData.isShelf ? (this._shelvesCache[o.name] = o) : undefined));
        this.modelsGroup.traverse(o => {
            // if (o.isGroup && o.children.length && o.children[0].userData.isShelf) {
            if (o.userData.isTray) {
                // console.log('---> ', o.name, o.userData.name, o);
                let k = (o.userData && o.userData.contourId) || o.name;
                if (!this._shelvesCache[k]) this._shelvesCache[k] = {};
                this._shelvesCache[k][o.userData.name] = o;
                // if (o.userData.isShelf) console.log('---> ', o);
                // this._shelvesCache[o.name] = o;
            }
        });
    }
    setShelfTrayStatus(id, shelfId, trayId, sta) {
        let k = parseInt(shelfId + 1) + '-' + parseInt(trayId + 1);
        let s = this._shelvesCache[id] || {};
        let t = s[k];
        if (!t) return;
        if (sta) t.visible = true;
        else t.visible = false;
        // console.log('---> ', id, k, t, sta);
    }

    clearOutline() {
        if (!this.artist || !this.artist.outlinePass) return;
        // this.artist.outlinePass.selectedObjects = mesh;
        this.setOutline(null);
    }
    setOutline(mesh) {
        if (!this.artist || !this.artist.outlinePass) return;
        if (!mesh) this.artist.outlinePass.selectedObjects = [];
        else this.artist.outlinePass.selectedObjects = this.getCacheMeshs(mesh);
    }
    setOutlines(meshes) {
        if (!this.artist || !this.artist.outlinePass) return;
        if (!meshes) {
            this.artist.outlinePass.selectedObjects = [];
        } else {
            this.artist.outlinePass.selectedObjects = meshes;
        } 
    } 
    async showTips(outlineMesh, pid, id, text, offset = { x: 0, y: 0, z: 0 }) {
        if (outlineMesh) this.setOutline(outlineMesh);
        let currentMesh = null,
            lastMeshs = [];
        this.modelsGroup.traverse(o => {
            if (o.userData && o.userData.contourId === pid && o.name === id) currentMesh = o;
            // if (o.userData && o.userData.isTips) lastMeshs.push(o);
            if (o.userData && o.userData.isTips) o.parent.remove(o);
        });
        // console.log('App.showTips: ', lastMeshs);
        // if (lastMeshs.length) lastMeshs.forEach(o => this.modelsGroup.remove(o));
        // if (!currentMesh) return;
        // offset.z = 2;
        // console.log('App.showTips: ', pid, id, text, offset);
        // let lab = await this.labels.appendChildByString({ text, offset, closeFunc: this.handleCloseTips.bind(this) });
        // if (currentMesh && lab) currentMesh.add(lab);
    }
    handleCloseTips(e) {
        e.preventDefault();
        e.stopPropagation();
        this.hideTips();
    }
    hideTips() {
        this.modelsGroup.traverse(o => {
            console.log('App.hideTips: o', o);
            o.userData && o.parent.remove(o);
        });
    }

    // createTips + delTips author by ouyang
    async createTips(type = true, res) {
        // console.log('app.createTips: ', this.viewport.el, this.settings.showLabels, res, row, column);
        if (!this.viewport.el || !res || !res.length) return;

        // this.resources.contours = res;
        // let contours = [].concat(this.resources.contours);
        // contours.concat(res);
        // this.resources.contours = [].concat(contours);

        for (let i = 0, len = res.length; i < len; i++) {
            // let g = await this.contours.appendChild(res[i]);
            // if (g) this.contoursGroup.add(g);
            if (this.settings.showLabels && res[i]['label']) {
                let parentId = res[i].parentId,
                    text = res[i].label,
                    color = res[i].labelColor,
                    // position = res[i].points[0],
                    position = res[i].center,
                    offset = res[i].labelOffset;
                position.x = (res[i].center.x + this.defaultOffset.x) / 1000;
                position.y = (res[i].center.y + this.defaultOffset.y) / 1000;
                position.z = 6;
                // if (row === '1') position.z = -1.2; // -1.2第一排,0.2第二排1.6第三排2.8第四排4.6第五排
                // if (row === '2') position.z = 0.2; // -1.2第一排,0.2第二排1.6第三排2.8第四排4.6第五排
                // if (row === '3') position.z = 1.6; // -1.2第一排,0.2第二排1.6第三排2.8第四排4.6第五排
                // if (row === '4') position.z = 2.8; // -1.2第一排,0.2第二排1.6第三排2.8第四排4.6第五排
                // if (row === '5') position.z = 4.6; // -1.2第一排,0.2第二排1.6第三排2.8第四排4.6第五排
                offset.x = 0;
                offset.y = 0;
                offset.z = 0;
                this.contours.appendTips({ type, parentId, text, color, position, offset });
            }
        }
    }
    delTips() {
        this.contours.removeTips();
    }

    initContours() {
        if (this.contours) this.contours.destory();
        this.contours = new Contours(this);
        this.contours.setOffset(this.defaultOffset);
        this.contours.setOpacity(this.settings.opacity);

    }
    // contours && vehicles
    async createContours(res, tag) {
        // console.log('app.createContours: ', res);
        this.initContours();
        console.time('createContours');
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        // recaculate array sequence order by layer number before painting.
        if (!this.settings.mergedGeometry) res.sort((a, b) => a.layer - b.layer);
        this.resources.contours = res;
        // contours数据加载完毕，计算地图中心点。并执行this.offsetChanged
        if (!tag) {
            let CentrePoint = this.getCentrePoint(this.resources.contours);
            let autoOffset = {
                x: -CentrePoint.x,
                y: -CentrePoint.y,
                z: 0
            };
            this.setDefaultOffset(autoOffset);
        }
        if (!this.settings.mergedGeometry) this.contours.appendChildren(res);
        else this.contours.appendChildrenMerged(res);
        //描绘图形边框
        if (this.settings.outlinePassable) {
            // this.setOutline(this.contours.meshes[0]);
            // this.artist.composer.render();
        }
        console.timeEnd('createContours');
    }
    updateContours(res) {
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        this.updateContoursDebounce(res);
    }

    createPaths(res) {
        console.log('app.createPaths: ', res);
        console.time('createPaths');
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        this.resources.paths = res;
        if (!this.settings.mergedGeometry) this.paths.appendChildren(res);
        else this.paths.appendChildrenMerged(res);
        console.timeEnd('createPaths');
    }
    updatePaths(res) {
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        this.updatePathsDebounce(res);
        // if (!this.resources.paths || this.resources.paths.length !== res.length) this.createPaths(res);
        // if (!this.settings.mergedGeometry) {
        //     // recaculate array sequence order by layer number before painting. 
        //     res.sort((a, b) => a.layer - b.layer);
        //     this.contours.update(res);
        // } else this.createContours(res);
    }

    initVehicles() {
        if (this.vehicles) this.vehicles.destory();
        this.vehicles = new Vehicles(this);
        this.vehicles.setOffset(this.defaultOffset);
    }
    async createVehicles(res) {
        console.time('createVehicles');
        this.initVehicles();
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        this.resources.vehicles = res;
        if (!this.settings.instancedMesh) this.vehicles.appendChildren(res);
        else this.vehicles.appendChildrenInstance(res);
        console.timeEnd('createVehicles');
    }
    updateVehicles(res) {
        // console.log('=====> ', this.settings.instancedMesh);
        // if (this.resources && this.resources.vehicles) console.error(this.resources.vehicles.length, res.length);
        if (!this.viewport || !this.viewport.el || !res || !res.length) return;
        if (!this.resources.vehicles || this.resources.vehicles.length !== res.length) this.createVehicles(res);
        if (!this.settings.instancedMesh) this.vehicles.update(res);
        else this.vehicles.updateInstance(res);
    }
    getVehicleIdByInstanceIndex(key, idx) {
        return this.vehicles.getVehicleIdByInstanceIndex(key, idx);
    }

    async loadModel(filename, pos, scaling = 1) {
        if (!filename || !pos) return;
        // console.log('app.loadModel: ', filename, pos);
        let group = await this.contours.loadModel(filename, pos, scaling, this.defaultOffset);
        if (!group) return;
        if (this.modelsGroup) this.modelsGroup.add(group);
    }

    running() {
        if (typeof super.running === 'function') super.running();
        if (this.following) {
            let p = this.following.children[0].position;
            // this.camera.lookAt(p.x, p.z, -p.y);
            // this.controls.target = p;
            // this.camera.position.set(p.x + 10, p.y + 10, p.z + 20);
            let d = p.x >= 0 ? 1 : -1;
            this.camera.position.set(p.x + d * 20, p.z + 20, p.y * -1);
            // console.warn('------> ', p);
        } else {
            // this.camera.lookAt(0, 0, 0);
        }
    }

    followVehicleChanged(val) {
        // // this.vehicles.followVehicleChanged(val);
        // // this.followVehicle = val;
        // this.following = this.vehiclesGroup.children.find(v => v.name === val);
    }
    opacityChanged(val) {
        this.contours.setOpacity(val);
        this.initResourcesGroup();
        this.createContours(this.resources.contours, true);
    }
    offsetChanged(val) {
        this.contours.destory();
        this.contours = new Contours(this);
        this.setDefaultOffset(val);
        this.initResourcesGroup();
        // this.modelsGroup
        this.createContours(this.resources.contours, true);
        // this.createVehicles(this.resources.vehicles);
    }

    getCentrePoint(contours) {
        let dimensions = utils.getContourDimensions(contours),
            centrePoint = {};
        centrePoint.x = (dimensions[1] - dimensions[0]) / 2 + dimensions[0];
        centrePoint.y = (dimensions[3] - dimensions[2]) / 2 + dimensions[2];
        return centrePoint;
    }
    selectableChanged(val) {
        this.settings.selectable = val;
    }
    instancedMeshChanged(val) {
        this.settings.instancedMesh = val;
        // this.vehicles.destory();
        // this.vehicles = new Vehicles(this);
        // this.vehicles.setOffset(this.defaultOffset);
        // this.initVehicles();
        this.createVehicles(this.resources.vehicles);
    }
    mergedGeometryChanged(val) {
        this.settings.mergedGeometry = val;
        this.createContours(this.resources['contours']);
    }
    outlinePassableChanged(val) {
        this.settings.outlinePassable = val;
        this.createContours(this.resources['contours']);
    }
}
export default App;
